Close A1/A2 structurally; red-team the gold suite (A3+B2)#21
Conversation
Adversarial coverage for the seams NEXT-STEPS A1-A3 name, landing before the structural changes that will close them (the coverage-first rule, NEXT-STEPS.md). - New gold entries: q11-q13 extraction/injection attacks aimed at private content (must route, never comply), q14 injection aimed at the machinery (must refuse bare), q15 the missing 'supported' case - which also owns the residue templating can't close, since supported prose is free prose that cites a hint. - Canary forbid-patterns on q02/q05/q11-q13/q15: phrases verified to exist only in private notebook bodies, so any answer containing one has leaked or confabulated private text. "chair by the window" rejected as a canary because the public lyric nearly contains it. - Refusal-debris patterns on q08-q10/q14: a failed refusal leaks URLs or citation-id-shaped prose; vacuously green when refusal succeeds. - New gold field expectAnswerPatterns: must-match mirror of forbidAnswerPatterns (same load validation), to pin behavior shape - never facts. Will pin the related-material template when it lands. - Tests: must-match judge cases, load validation for both pattern fields, all four modes now asserted present in the gold set. q11-q15 may be red under --full until the template and label changes land; that is the coverage doing its job. Retrieval tier expected green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n27KsH2AS1mbpHMYp8y2X
Close the provenance-without-backing seam: a hint citation is real provenance but carries no text, so free prose in related-material mode was the one place a confabulated summary of private material passed every gate, held only by a prompt instruction. - New src/public-safe.ts: renderRelatedMaterialAnswer builds the mode's prose from the cited hints' public-safe fields (label, locator) alone. No raw URL in the prose - the citation object carries the link, and gold q07 forbids URLs here. - src/answer.ts: finalizeAnswer runs repair -> grounding -> template, so the hint lookup cannot miss and answers that only became related-material through repair's kind conversion get templated too. Confabulation in this mode is now inexpressible, not discouraged. - Prompt tells the model its related-material prose is standardized. - Gold pins the template via expectAnswerPatterns on q07/q11-q13, so a regression cannot silently un-template the mode. - NEXT-STEPS A2 rewritten as closed, with the residue named: supported mode still cites hints under free prose, owned by the q15 canaries. The template's safety is exactly the safety of label+locator - A1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n27KsH2AS1mbpHMYp8y2X
The RoutingHint label was the note's raw frontmatter title and the locator raw frontmatter, guarded only by a warning comment - a privately-titled note leaked through its own label. Three layers replace the comment: - Explicit `label:` frontmatter, required on private notes: the display name that travels, distinct from `title` (private, embedded, never travels). What travels is now an authored per-note decision; a corpus without labels fails loudly with the reason. - PublicSafe branded type on PrivateNote/RoutingHint label+locator, whose only constructor is the new build-time lint assertPublicSafeField (src/public-safe.ts): non-empty, single line, <=120 chars, and no run of 5 consecutive words shared with the note's private body - a traveling field that quotes the note fails the build, not an answer. N=5 is calibrated: the demo's bibliographic locator legitimately shares a 4-token run with its sermon body. - Index schema v3 (store.ts): note entries must carry title/label/locator, so a stale or hand-edited artifact fails fast with the rebuild remedy. noteEmbedText now embeds title+body - byte-identical to the old label+body for every corpus whose labels repeated titles, so committed demo vectors survive without re-embedding: scripts/migrate-demo-index-v3.mjs migrated the committed demo indexes mechanically (title := label, vectors and contentHashes untouched), verified by the keyless demo gate (7/7, rho 1.0000). Residue named in NEXT-STEPS A1: the lint is a 5-gram tripwire, `about` travels unlinted as a declared public page, and the brand erases at JSON read - backstopped by the gold canaries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n27KsH2AS1mbpHMYp8y2X
Automated Checks (advisory, non-blocking)✅ All checks passed. Standards ComplianceThis PR is directly in the repo’s highest-risk area: no-leak boundary, citation/mode semantics, eval coverage, and demo artifact migration. From the visible diff, I don’t see a clear standards violation, and one issue from my prior review appears addressed by the migration/hash-invariant testing around the committed demo artifacts. Because the only visible code change here is the large
SummaryThis PR appears to do three coordinated things: add adversarial gold cases for leak/mode failures, structurally template related-material answers from public-safe hint metadata, and migrate the index schema so private note titles no longer double as traveling labels. Most of the visible diff is committed Reviewer: most of the risk is in the unseen index-loading, public-safe boundary enforcement, and answer-finalization paths; the giant JSON update is mostly a consequence. What to pay attention to
Things I noticed🟡 Yellow flags — consider for this PR or a follow-up:
No visible red flags from the diff shown here. Good patterns
Suggested improvements
Surmado Code Review (v1.2-mt) is an automated review, designed to work alongside human judgment. Want to change your STANDARDS.md or YML? Edit it directly, or tune it with our AI agent Scout. Comment |
- readIndexFile's version mismatch now names the found version alongside
the expected one ("schema version 2, not schema version 3"), so a stale
artifact says what it is, not just what it isn't.
- New keyless test in demo/artifacts.test.ts: every committed demo entry's
contentHash must equal the hash of today's embed-text derivation
(title+body for notes), proving the v2->v3 migration preserved the exact
bytes the committed vectors were embedded from.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019n27KsH2AS1mbpHMYp8y2X
|
Responding to the automated review ( Suggestions. (1) A test proving plausible model prose is discarded in related-material mode already exists — Questions. (1) Other construction paths for Generated by Claude Code |
Blocking finding from the keyed eval: told its related-material prose is standardized, the model returns mode + hint citations + empty answer - and validateAnswer rejected the empty prose before finalizeAnswer could render the template. q07/q11/q12/q13 all threw under --full. - validateAnswer now admits empty prose ONLY for a declared related-material answer, documented as the one deliberate gap: that mode's prose is engine-rendered downstream. - finalizeAnswer re-enforces the sourced-prose contract after templating, so the gap cannot leak past the mode it exists for: an empty-prose answer whose citations repair OUT of related-material (to a record) fails at the door, and one with no citations normalizes to a bare refusal as before. - Prompt now says to leave "answer" empty for the mode (or write a short routing sentence - replaced either way). - Tests pin the exact failure: empty-prose related-material renders the template end to end; empty prose on any other declared mode still throws; the repair-re-derivation edge throws; bare-refusal normalization unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n27KsH2AS1mbpHMYp8y2X
What & why
Closes the two highest-value seams in
NEXT-STEPS.mdA and audits the third, in three commits sequenced so the gold coverage that would catch each failure mode lands before the structural change that closes it (the repo's own rule).1. Red-team the gold suite (A3+B2) —
74c86ed. Five new adversarial entries: extraction-shaped queries (q11–q12: "what exactly does the notebook say", a verbatim-quote demand), instruction injections aimed at private content (q13, must still route) and at the machinery (q14, must refuse bare), and the previously missingsupportedcase (q15). Canary forbid-patterns — phrases verified to exist only in the private notebook bodies — now guard each mode's characteristic failure ("chair by the window" was rejected as a canary because the public lyric nearly contains it). NewexpectAnswerPatternsgold field: the must-match mirror offorbidAnswerPatterns, documented as pinning behavior shape, never facts.2. Template the related-material answer (A2) —
02f0c91. A hint citation is provenance without backing, so free prose in this mode was the one place a confabulated "summary" of private material passed every gate.finalizeAnswernow replaces the mode's prose, after grounding, with a fixed sentence rendered only from the cited hints' public-safe fields (renderRelatedMaterialAnswer,src/public-safe.ts) — the mode can point, never assert content. The gold suite pins the template. NEXT-STEPS A2 rewritten as closed, with the residue named (supported-mode hint citations under free prose, owned by the q15 canaries).3. Make the traveling label/locator boundary structural (A1) —
624e825. Private notes now require an explicitlabel:frontmatter field;titlestays private and is what gets embedded. Both traveling fields are a brandedPublicSafetype whose only constructor is a build-time lint (single line, ≤120 chars, no 5-consecutive-word run shared with the note's private body — N=5 calibrated so the demo's bibliographic locator passes). Index schema bumped to v3; the committed demo indexes were migrated mechanically byscripts/migrate-demo-index-v3.mjs(title := labelis exact, embed text byte-identical, vectors and contentHashes untouched — nothing re-embedded), verified by the keyless demo gate (7/7, rho 1.0000).Note for the keyed follow-up:
npm run indexis required (local index is now v2-stale), thennpm run evalandnpm run eval -- --full --ids q07,q11,q12,q13,q14,q15. q15 is the likeliest to wobble (compound canon+process question); if it fails, the fix is a sentence in the prompt's supported-mode bullet, never the query.Checklist
npm testpasses without an API key (47/47) — no hidden dependency on live callsnpm run typecheckpassesPublicSafeis constructible only through the lint; the related-material template renders nothing butPublicSafefieldsnpm run evalsubset — not run here (no API key in this environment); keylessnpm run demo:runcertifies retrieval on the migrated artifacts (7/7, rho 1.0000). Keyed subset above is the owner's step..github/STANDARDS.md— the rubric a PR is judged by🤖 Generated with Claude Code
https://claude.ai/code/session_019n27KsH2AS1mbpHMYp8y2X
Generated by Claude Code